﻿using Daemon.Common;
using Daemon.Common.Attribute;
using Daemon.Common.Auth;
using Daemon.Data.Infrastructure.Auth;
using Daemon.Data.Substructure.Enums;
using Daemon.Middleware;
using Daemon.Model;
using Daemon.Model.Entities;
using Daemon.Model.Entities.Wx;
using Daemon.Model.Jwt;
using Daemon.Model.ViewModel;
using Daemon.Repository.Contract;
using Daemon.Service.Contract;
using DaemonAPI.Helpers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace DaemonAPI.Controllers.V1.TakeAway
{
    [ApiController]
    [Route("api/[controller]/[action]")]
    public class WxAuthController : ControllerBase
    {
        private readonly IHttpClientFactory _httpClientFactory;

        private readonly IClientConfigRepository _clientConfigRepository;

        private readonly ITakeAwayUserRepository _userRepository;

        private readonly IHttpContextAccessor _httpContextAccessor;

        private readonly IUserSessionRepository _userSessionRepository;

        private readonly ITakeAwayUserAccountRepository _takeAwayUserAccountRepository;

        private readonly IUserAuthorizationService _userAuthorizationService;


        public WxAuthController(IClientConfigRepository clientConfigRepository, IHttpClientFactory httpClientFactory, ITakeAwayUserRepository userRepository,
            IUserSessionRepository userSessionRepository,
            IHttpContextAccessor httpContextAccessor,
            ITakeAwayUserAccountRepository takeAwayUserAccountRepository,
            IUserAuthorizationService userAuthorizationService)
        {
            _clientConfigRepository = clientConfigRepository;
            _httpClientFactory = httpClientFactory;
            _userRepository = userRepository;
            _httpContextAccessor = httpContextAccessor;
            _userSessionRepository = userSessionRepository;
            _takeAwayUserAccountRepository = takeAwayUserAccountRepository;
            _userAuthorizationService = userAuthorizationService;
        }

        [HttpPost]
        [Route("")]
        [AllowAnonymous]
        public async Task<ResultModel> Auth(WxAuthRequest model)
        {
            var clientConfig = _clientConfigRepository.Find();
            var wxAuthUrl = $"https://api.weixin.qq.com/sns/jscode2session?appid={clientConfig.AppId}&secret={clientConfig.AppSecret}&js_code={model.Code}&grant_type=authorization_code";
            var client = _httpClientFactory.CreateClient();
            client.BaseAddress = new Uri(wxAuthUrl);
            var response = await client.GetAsync(wxAuthUrl);
            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                var content = await response.Content.ReadAsStringAsync();
                var sessionCode = JsonConvert.DeserializeObject<WxCodeSession>(content);
                if (sessionCode.ErrCode != 0)
                {
                    return new ResultModel(HttpStatusCode.Unauthorized, "");
                }
                var registerUser = Register(model, sessionCode.Session_key, sessionCode.OpenId);
                var loginUser = new BaseAuthUser() { Id = registerUser.Id, UserType = "WX", UserName = registerUser.UserName };
                var tokenResult = TokenHelper.CreateToken(registerUser.UserName, "WX", loginUser);
                loginUser.UserType = "WX";
                AddLoginInfo(loginUser);
                var authInfo = new AuthInfoEF(loginUser);
                ServiceLocator.Resolve<ClientAuthCache>().AddClientUserEntryAsync("WX" + "_" + loginUser.UserName, authInfo);
                return new ResultModel(HttpStatusCode.OK, "", new { Token = tokenResult, UserInfo = authInfo });
            }

            return new ResultModel(HttpStatusCode.Unauthorized, "");
        }

        private TakeAwayUser Register(WxAuthRequest model, string sessionKey, string openId)
        {
            var existUser = _userRepository.FindAll().FirstOrDefault(r => r.OpenId == openId);
            if (existUser != null)
            {
                return existUser;
            }

            var userInfo = JsonConvert.DeserializeObject<WxUserModel>(AESDecrypt(model.EncryptedData, sessionKey, model.Iv));
            var addedUser = new TakeAwayUser();
            addedUser.OpenId = openId;
            addedUser.ParentId = model.ParentId;
            addedUser.UserName = userInfo.NickName;
            addedUser.Avatar = userInfo.AvatarUrl;
            var registerUser = _userRepository.AddByDBContext(addedUser);
            var userAccount = new TakeAwayUserAccount() { UserId = registerUser.Id, Integeral = 0, Money = 0 };
            _takeAwayUserAccountRepository.AddByDBContext(userAccount);
            return registerUser;
        }

        private void AddLoginInfo(BaseAuthUser user)
        {
            var session = new UserSession()
            {
                UserId = user.Id,
                IP = HttpContextExtension.GetClientIPAddress(_httpContextAccessor.HttpContext.Request),
                LoginTime = DateTime.UtcNow,
            };

            _userSessionRepository.SetLogoutTimeByUser(user.Id, LogoutReasonEnum.LoggedInOnOtherDevice); // To handle do login on same time, mark the older one Log out
            _userSessionRepository.AddByDBContext(session);
        }

        private string AESDecrypt(string encryptedData, string sessionKey, string iv)
        {
            try
            {
                var encryptedDataByte = Convert.FromBase64String(encryptedData);  // strToToHexByte(text);
                var rijndaelCipher = new RijndaelManaged
                {
                    Key = Convert.FromBase64String(sessionKey),
                    IV = Convert.FromBase64String(iv),
                    Mode = CipherMode.CBC,
                    Padding = PaddingMode.PKCS7
                };

                var transform = rijndaelCipher.CreateDecryptor();
                var plainText = transform.TransformFinalBlock(encryptedDataByte, 0, encryptedDataByte.Length);
                var result = Encoding.Default.GetString(plainText);

                return result;
            }
            catch (Exception ex)
            {
                return null;

            }
        }
    }
}
